/***************************************************************************
 *   Copyright (C) 2009 by Zheng Yonglei
 *   zhengyonglei@gmail.com   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include "TestManager.h"
#include "TrainDataManager.h"
#include "Logger.h"

TestManager::TestManager() :
	mReviewCycle(0),
	mReviewFrequency(0)
{
}

TestManager::~TestManager()
{
	saveTest();
}

void TestManager::finishCurTask()
{
	LOG<<"Finishing current task"<<endl;

	mTotalTestCount = 0;
	mTestCandidateType = TS_New;

	saveTest();
	mTestingWordVect.clear();
	mFinishedWordVect.clear();
	mNewWordVect.clear();
	mTestFinishedFirstVect.clear();

	mReviewCycle = 0;
	mReviewFrequency = 0;

	WordManager::finishCurTask();
}

void TestManager::addWord(int id)
{
	mWordsVector.push_back(id);
	mWordsMap.insert(make_pair(id, new TestWord(id)));
}

TestWord* TestManager::getCurTestWord()
{
	Word* word = getCurWord();
	if (!word)
		return NULL;

	return dynamic_cast<TestWord*>(word);
}

TestWord* TestManager::getTestWord(int id)
{
	Word* word = getWord(id);
	if (!word)
		return NULL;

	return dynamic_cast<TestWord*>(word);
}

bool TestManager::prepareToTest()
{
	LOG<<"Preparing to start a new test"<<endl;
	bool loadSuccess = loadTest();
	WordManager::getBasicInfoByActiveState();
	if (mWordsMap.empty())
		return false;

	if (!loadSuccess)
	{
		mTestFinishedFirstVect.clear();
		QString command;
		QTextStream cs(&command);
		cs<<" select id "
				" from test_records "
				" where "
				" is_active=1 "
				" and ";
		cs<<" test_state="<<TS_Finished<<";";
		DB.quickGet(command);
		for (int i=0; i<DB.getRowCount(); i++)
		{
			int id = DB.getTableValue(i,"id").toInt();
			mTestFinishedFirstVect.push_back(id);
		}
	}

	return true;
}

bool TestManager::testNextWord()
{
	LOG<<"Test next word"<<endl;
	QString command;

	updateTestStateCount();
	selectTestCandidateType();

	mCurWordID = selectTestWord();

	//check if no proper word was found
	if(mCurWordID < 0 && mTestCandidateType == TS_Testing)
	{
		updateTestWordFrozenInfo();
		mTestCandidateType = TS_Finished;
		LOG<<"Force to set mTestCandidateType to TS_Finished"<<endl;
		mCurWordID = selectTestWord();
	}
	else if(mCurWordID < 0 && mTestCandidateType == TS_Finished)
	{
		updateTestWordFrozenInfo();
		mTestCandidateType = TS_Testing;
		LOG<<"Force to set mTestCandidateType to TS_Testing"<<endl;
		mCurWordID = selectTestWord();
	}

	LOG<<"mCurWordID = "<<mCurWordID<<endl;
	if(mCurWordID < 0)
	{
		LOG<<"Invalid mCurWordID = "<<mCurWordID<<". Try to reset cooling_time_remain values to solve this problem."<<endl;

		vector<int> validWords;
		for (int i=0; i<mTestingWordVect.size(); i++)
			if (mTestingWordVect[i] != mLastWordID)
				validWords.push_back(mTestingWordVect[i]);
		for (int i=0; i<mFinishedWordVect.size(); i++)
			if (mFinishedWordVect[i] != mLastWordID)
				validWords.push_back(mFinishedWordVect[i]);

		QString command = QString("update test_records set cooling_time_remain=0 where ");
		for (int i=0; i<validWords.size()-1; i++)
			command.append(QString(" id=%1 or ").arg(validWords[i]));
		command.append(QString(" id=%1;").arg(validWords[validWords.size()-1]));
		DB.quickUpdate(command);

		mCurWordID = selectTestWord();
	}

	if(mCurWordID < 0)
	{
		LOG<<"Fatal error: Invalid mCurWordID = "<<mCurWordID<<" after reset cooling_time_remain values."<<endl;
		return false;
	}
	else
	{
		int index = mTestFinishedFirstVect.indexOf(mCurWordID);
		if (index != -1)
			mTestFinishedFirstVect.removeAt(index);

		getCurWordTestData();
		mLastWordID = mCurWordID;
		return true;
	}
}

bool TestManager::saveTest()
{
	if (DB.curUsr.isEmpty())
	{
		LOG<<"Error! No user logined"<<endl;
		return false;
	}

	QString usrDir = USER_DIR + QString("%1/test.conf").arg(DB.curUsr);
	QSettings settings(usrDir , QSettings::IniFormat );
	QVariant val;
	QStringList wordIDList;

	settings.beginGroup( "Test" );
	//save active words
	QList<int> activeWords;
	QString command = "select id from test_records where is_active=1;";
	DB.quickGet(command);
	for (int i=0; i<DB.getRowCount(); i++)
	{
		int id = DB.getTableValue(i, "id").toInt();
		activeWords.push_back(id);
	}

	wordIDList.clear();
	for (int i=0; i<activeWords.size(); i++)
		wordIDList<<QString("%1").arg(activeWords[i]);
	settings.setValue("ActiveWords", QString("%1").arg(wordIDList.join("|")));

	//save mTestFinishedFirstVect
	wordIDList.clear();
	for (int i=0; i<mTestFinishedFirstVect.size(); i++)
		wordIDList<<QString("%1").arg(mTestFinishedFirstVect[i]);
	settings.setValue("TestFirst", QString("%1").arg(wordIDList.join("|")));

	settings.endGroup();

	return true;
}

bool TestManager::loadTest()
{
	if (!resumeTest)
		return false;

	resumeTest = false;

	QString usrDir = QString("./data/user/%1/test.conf").arg(DB.curUsr);
	QSettings settings(usrDir , QSettings::IniFormat );
	QVariant val;
	QStringList wordIDList;

	QList<int> activeWords;
	settings.beginGroup( "Test" );
	//resume active words
	val = settings.value("ActiveWords");
	if(val.isValid()) wordIDList = val.toString().split("|", QString::SkipEmptyParts);
	for (int i=0; i<wordIDList.size(); i++)
	{
		int id = wordIDList[i].toInt();
		activeWords.push_back(id);
	}

	if (activeWords.empty())
		return false;

	QString command;
	QTextStream cs(&command);
	DB.quickUpdate("BEGIN");
	for (int i=0; i<activeWords.size(); i++)
	{
		command.clear();
		cs<<"update test_records set is_active=1 where id="<<activeWords[i]<<";"<<endl;
		DB.quickUpdate(command);
	}
	DB.quickUpdate("COMMIT");

	//resume mTestFinishedFirstVect
	mTestFinishedFirstVect.clear();
	val = settings.value("TestFirst");
	if(val.isValid()) wordIDList = val.toString().split("|", QString::SkipEmptyParts);
	for (int i=0; i<wordIDList.size(); i++)
	{
		int id = wordIDList[i].toInt();
		if (activeWords.contains(id))
			mTestFinishedFirstVect.push_back(id);
	}

	settings.endGroup();

	if (mTestFinishedFirstVect.empty())
		return false;
	else
		return true;
}

void TestManager::getCurWordTestData()
{
	TestWord* testWord = getCurTestWord();
	if (testWord)
	{
		QString command;

		command.clear();
		vector<QString>recitingInfo;
		recitingInfo.push_back("test_state");
		recitingInfo.push_back("test_record");
		recitingInfo.push_back("successive_pass_count");
		command.append("select ");
		for(int i=0; i<recitingInfo.size()-1; i++)
			command.append(recitingInfo[i]).append(",");
		command.append(recitingInfo[recitingInfo.size()-1]);
		command.append(QString(" from test_records where id = %1").arg(mCurWordID));
		DB.execQuery(command, QueryT_Read, DB.curUsr);

		int test_state = DB.getTableValue(0,"test_state").toInt();
		QString test_record = DB.getTableValue(0,"test_record");
		int successive_pass_count = DB.getTableValue(0,"successive_pass_count").toInt();

		testWord->state = (TestState)test_state;
		testWord->successivePassCount = successive_pass_count;
		testWord->setTestRecords(test_record);
	}
}

void TestManager::updateCurWordTestData()
{
	TestWord* testWord = getCurTestWord();
	if (testWord)
	{
		TestState test_state = testWord->state;
		int successive_pass_count = testWord->successivePassCount;
		int cooling_time_remain = testWord->coolingTimeRemain;
		QString test_record = testWord->getTestRecordString();

		QString command;
		QTextStream cs(&command);

		cs<<" update test_records "
				" set ";
		cs<<" test_state ="<<test_state<<", ";
		cs<<" successive_pass_count ="<<successive_pass_count<<", ";
		cs<<" cooling_time_remain ="<<cooling_time_remain<<", ";
		cs<<" test_record ='"<<test_record<<"' ";
		cs<<" where id="<<testWord->ID<<";";
		DB.quickUpdate(command);
	}
}

void TestManager::updateCurWordTrainData()
{
	TestWord* testWord = getCurTestWord();
	if (testWord)
	{
		QString word = testWord->word;
		QString date = OPT.getCurrendDate().toString(DATE_FORMAT);
		QString command;
		QTextStream cs(&command);
		//check if update needed
		cs<<	" select "
					" test_data_id "
					" from "
					" test_train_data "
					" where "
					" test_word='"<<word<<"' "
					" and test_date='"<<date<<"'; ";
		DB.quickGet(command);
		if (DB.getRowCount() > 0)
			return;

		//update
		LOG<<"Updating training data of current testing word"<<endl;
		testWord->updateTrainingData();
		int totalTestCount = testWord->getTestRecordMap().size();
		int lastTestInterval = testWord->lastTestInterval;
		int testHistoryScore = testWord->testHistoryScore;
		bool testResult = testWord->passLastTest;

		command.clear();
		cs<<"insert into test_train_data ("
				"	test_word, "
				"	test_date, "
				"	test_interval, "
				"	test_count, "
				"	test_record_history_score, "
				"	test_result"
				") ";
		cs<<"values(";
		cs<<"'"<<word<<"', ";
		cs<<"'"<<date<<"', ";
		cs<<lastTestInterval<<", ";
		cs<<totalTestCount<<", ";
		cs<<testHistoryScore<<", ";
		cs<<testResult<<");";

		DB.quickUpdate(command);
	}
}

void TestManager::updateTestWordFrozenInfo()
{
	QString command;
	QTextStream cs(&command);
	QString state;

	if (mTestCandidateType == TS_New)
	{
		mTestCandidateType = TS_Finished;
		updateTestWordFrozenInfo();
		mTestCandidateType = TS_Testing;
		updateTestWordFrozenInfo();
		mTestCandidateType = TS_New;
	}

	command.clear();
	cs<<" select id,cooling_time_remain "
			" from test_records "
			" where is_active = 1 "
			" and test_state = "<<int(mTestCandidateType)<<";";
	DB.execQuery(command, QueryT_Read, DB.curUsr);

	int frozenWordCount = 0;
	DB.quickUpdate("BEGIN");
	for(int i=0; i<DB.getRowCount(); i++)
	{
		int id = DB.getTableValue(i,"id").toInt();
		int cooling_time_remain = DB.getTableValue(i,"cooling_time_remain").toInt();
		Word* word = getWord(id);
		QString wordStr;
		if(word)
			wordStr = word->word;
		else
		{
			LOG<<"Inadequate information for word(id="<<id<<")"<<endl;
			assert(0);
		}

		if(cooling_time_remain > 1)
		{
			cooling_time_remain--;
			frozenWordCount++;
		}
		else if(cooling_time_remain == 1)
		{
			cooling_time_remain = 0;
		}
		else
			cooling_time_remain = 0;

		command.clear();
		cs<<" update test_records "
				" set cooling_time_remain="<<cooling_time_remain;
		cs<<" where id="<<id<<";";
		DB.execQuery(command, QueryT_Write, DB.curUsr);
	}
	DB.quickUpdate("COMMIT");
}

void TestManager::updateTestStateCount()
{
	QString command;

	mTestingWordVect.clear();
	command =  QString("select id from test_records where test_state =%1 and is_active = 1 ;").arg(int(TS_Testing));
	DB.execQuery(command, QueryT_Read, DB.curUsr);
	for (int i=0; i<DB.getRowCount(); i++)
		mTestingWordVect.push_back(DB.getTableValue(i,"id").toInt());

	mNewWordVect.clear();
	command =  QString("select id from test_records where test_state =%1 and is_active = 1 ;").arg(int(TS_New));
	DB.execQuery(command, QueryT_Read, DB.curUsr);
	for (int i=0; i<DB.getRowCount(); i++)
		mNewWordVect.push_back(DB.getTableValue(i,"id").toInt());

	mFinishedWordVect.clear();
	command =  QString("select id from test_records where test_state =%1 and is_active = 1 ;").arg(int(TS_Finished));
	DB.execQuery(command, QueryT_Read, DB.curUsr);
	for (int i=0; i<DB.getRowCount(); i++)
		mFinishedWordVect.push_back(DB.getTableValue(i,"id").toInt());
}

void TestManager::selectTestCandidateType()
{
	//check for new words
	if(mTestingWordVect.size() <= 10 && mNewWordVect.size() > 0)
	{
		mTestCandidateType =  TS_New;
		LOG<<"Candidate type is TS_New."<<endl;
		return;
	}

	//check for testing words
	if (mReviewCycle > 0)
		mReviewCycle--;

	if (mReviewCycle == 0)
	{
		LOG<<"mReviewCycle == 0. Need review."<<endl;
		setReviewFrequency();
		mTestCandidateType = TS_Testing;
		LOG<<"Candidate type is TS_Testing"<<endl;
		return;
	}
	else
	{
		LOG<<"mReviewCycle = "<< mReviewCycle <<". Do not need review."<<endl;
		mTestCandidateType = TS_Testing;
		LOG<<"Candidate type is TS_Testing"<<endl;
		return;
	}
}

void TestManager::setReviewFrequency()
{
	if (mFinishedWordVect.size() != 0 && mTestingWordVect.size() != 0)
	{
		mReviewFrequency = int(float(mTestingWordVect.size())/float(mFinishedWordVect.size())*50);
		if (mTestingWordVect.size() > 20)
			mReviewFrequency = 20;
		else if (mTestingWordVect.size() > 15)
			mReviewFrequency = 15;

		if (mReviewFrequency <2)
			mReviewFrequency = 2;
	}
	else
	{
		//previous algorithm
		if (mFinishedWordVect.size() < 5)
			mReviewFrequency = 30;

		if (mFinishedWordVect.size() >= 5 && mFinishedWordVect.size() <20)
			mReviewFrequency = 20;

		if (mFinishedWordVect.size() >= 20 && mFinishedWordVect.size() < 50)
			mReviewFrequency = 10;

		if (mFinishedWordVect.size() >= 50)
			mReviewFrequency = 5;
	}

	mReviewCycle = mReviewFrequency;
}

int TestManager::selectTestWord()
{
	QString command;
	vector<int>candidates;

	if(mTestCandidateType == TS_Finished && mTestFinishedFirstVect.size() > 0)
	{
		LOG<<"Select finished words that have not been tested."<<endl;
		for(int i=0; i<mTestFinishedFirstVect.size(); i++)
		{
			int id = mTestFinishedFirstVect[i];
			candidates.push_back(id);
		}
	}
	else
	{
		//LOG<<"Select testing or finished words."<<endl;
		command.clear();
		command.append(QString(""
				//"select id,difficulty_level,cooling_time_remain "
				"select id,cooling_time_remain "
				"from test_records "
				"where is_active = 1 and test_state = %1;").arg(int(mTestCandidateType)));
		DB.execQuery(command, QueryT_Read, DB.curUsr);

		for(int i=0; i<DB.getRowCount(); i++)
		{
			int id = DB.getTableValue(i,"id").toInt();
			int coolingTimeRemain = DB.getTableValue(i,"cooling_time_remain").toInt();
			if(coolingTimeRemain == 0)
				candidates.push_back(id);
		}
	}

	LOG<<"candidates.size() = "<<candidates.size()<<endl;
	if(candidates.size() == 0)
		return -1;

	int resultIndex = random(candidates.size());

	return candidates[resultIndex];
}

void TestManager::testPassed()
{
	getCurTestWord()->testPassed();
	curTestFinished();
}

void TestManager::testFailed()
{
	getCurTestWord()->testFailed();
	curTestFinished();
}

void TestManager::curTestFinished()
{
	int frozenFactor = getWordCountByTestState(getCurTestWord()->state)/2;
	getCurTestWord()->setFrozenRemainCycle(frozenFactor);
	mTotalTestCount++;
	updateCurWordTestData();
	updateCurWordTrainData();
	updateTestWordFrozenInfo();
}

int TestManager::getActivatedWordCount()
{
	return mTestingWordVect.size() + mFinishedWordVect.size() + mNewWordVect.size();
}

int TestManager::getActivatedWordTotalFinishedCount()
{
	return mFinishedWordVect.size();
}

void TestManager::initActivatedWordTotalTestCount()
{
	mTotalTestCount = 0;
	for (int i=0; i<mWordsVector.size(); i++)
	{
		TestWord* testWord = getTestWord(mWordsVector[i]);
		assert(testWord);
		mTotalTestCount += testWord->calculateTotalTestCount();
	}
}

int TestManager::getActivatedWordTotalTestCount()
{
	return mTotalTestCount;
}

int TestManager::getWordCountByTestState(TestState status)
{
	switch(status)
	{
	case TS_New:
		return mNewWordVect.size();
	case TS_Finished:
		return mFinishedWordVect.size();
	case TS_Testing:
		return mTestingWordVect.size();
	}

	return 0;
}
